#ifndef _NET_IO_
#define _NET_IO_

#include "raknet\WindowsIncludes.h"
#include "aerial\Core\base.h"

#include "raknet\RakPeerInterface.h"
#include "raknet\BitStream.h"
#include "raknet\RakNetTypes.h"
#include "raknet\MessageIdentifiers.h"
#include "raknet\GetTime.h"
#include "raknet\RakSleep.h"
#include "raknet\DS_ThreadsafeAllocatingQueue.h"

#include "aerial\Core\NetMessage.h"

#include <process.h>
#include <iostream>

using namespace RakNet;
using namespace DataStructures;
using namespace std;

struct NetMessage;

#define BATTLE_SERVER_PORT	2012

namespace air {
	
	/**
	 * @brief Network Communication
	 */
	class NetIO {
	public:
		static ThreadsafeAllocatingQueue<Packet> Q;
		NetIO();
		~NetIO();
		
		/// @brief check whether given string conforms to a valid ip format
		static bool IsValidIP(const char*);
		/// @brief release all the corresponding resources and close the server thread
		static void Release();
		/**
		 * @brief get the player id by ip address
		 *
		 * @return player's id
		 */
		int findPlayerBySystemAddress(SystemAddress& sysAddr);

		void SetBattleServer(bool isServer) {	server = isServer;	}
		inline bool IsServer() const { return server; }
		
		void A_StartThread();
		void A_TerminateThread();
		/// @brief function for server to create a battle
		bool StartBattleServer();

		/// @brief function for client to create a battle
		bool ConnectToBattleServer(const char*);

		void ClearPlayerInfo();
		void CloseConnection();
		/**
		 * @brief battle-server-side function broadcast specified message.
		 *
		 * @param nMsg message to be broadcast
		 * @param playerId to be excluded from broadcast. playerId = -1 means all players
		 */
		void broadcastExcludePlayer(BitStream* bso, int playerId);
		void broadcastExcludeAddress(BitStream* bso, SystemAddress& sa);

		void DeSerializationPlayerInfo(BitStream* bsi, bool head = true);
		void SerializePlayerInfo(BitStream* bso, bool head = true);
		void SerializeSinglePlayerInfo(BitStream* bso, uchar id, bool head = true);
		int DeserializeSinglePlayerInfo(BitStream* bsi, bool head = true);

		void sendToBattleServer(BitStream* bso);
		void SendToSpecifiedAddress(BitStream*, SystemAddress&);
		void RegisterServerAddress(SystemAddress*);

		/// @brief receive message during battle scene
		Packet* recv();
		/// @brief receive message before battle scene
		Packet* m_recv();
		void Deallocate(Packet* p);
		void m_Deallocate(Packet* p);
		/**
		 * @brief reset the netIO after ending a battle
		 */		 		
		void reset();

		/**
		 * @brief function in another thread for receiving and sending packages
		 */
		static unsigned int __stdcall NetIO::NetworkProc(void *pParam);
		 
		// Communication with BattleScene
		uchar ChangePlayerType(uchar id) {
			if ( id < 0 || id >= MAX_PLAYER_COUNT ) return 0;
			if (playerInfo[id].flyObjectType < 3) ++playerInfo[id].flyObjectType;
			else playerInfo[id].flyObjectType = 1;
			return playerInfo[id].flyObjectType;
		}

		bool ToggleReadyState(uchar id)
		{
			if ( id < 0 || id >= MAX_PLAYER_COUNT ) return 0;
			playerInfo[id].ready = !playerInfo[id].ready;
			if (playerInfo[id].ready) ++readyCount; else --readyCount;
			return playerInfo[id].ready;
		}

		void SetPlayerReady( uchar id, bool _ready ) 
		{
			if ( id < 0 || id >= MAX_PLAYER_COUNT ) return;
			if (playerInfo[id].ready) 
			{
				if (!_ready) 
				{
					--readyCount;
					playerInfo[id].ready = false;
				}
			}
			else {
				if (_ready)
				{
					++readyCount;
					playerInfo[id].ready = true;
				}
			}	
		}

		void SetPlayerConfirm( uchar id )
		{
			if ( id < 0 || id >= MAX_PLAYER_COUNT ) return;
			if ( !playerInfo[id].confirm ) 
			{
				playerInfo[id].confirm = true;
				++confirmCount;
			}
		}

		uchar SetPlayerLost( uchar id )
		{
			if ( id < 0 || id >= MAX_PLAYER_COUNT ) return 0;
			if ( playerInfo[id].confirm ) 
			{
				--confirmCount;
			}
			playerInfo[id].available = false;
			--expectedPlayerCount;
			return playerInfo[id].boardId;
		}

		uchar getMeId() { return me; }
		void SetMeId(int _me) { me = _me; }
		inline void SetPlayerCount(int _playerCount) { expectedPlayerCount = _playerCount; }
		inline uchar GetPlayerCount() const { return expectedPlayerCount; }
		inline PlayerDescriptor* GetPlayerInfo() { return playerInfo; }
		int AddPlayer(RakString userName, SystemAddress sa, int boardId);

		inline bool IsLocked() { return lock; }
		void Lock() { lock = true; }
		
		/// @brief interface to RakNet
		static RakPeerInterface *node;		 
		/// @brief only for server. record how many players are ready
		uchar readyCount;
		uchar confirmCount;
	private:
		bool server;
		bool lock;
		/// @brief my id in the game
		uchar me;
		uchar expectedPlayerCount;
		PlayerDescriptor playerInfo[10];

		static HANDLE hThreadRakNet;
		unsigned __int32 NetWorkProcAddr;
		//// @brief store the message queue
		
		/// @brief show whether another thread is running
		static bool running;
		SystemAddress battleServerAddress;
	};
}

#endif